package controllers
{
	import flash.events.DataEvent;
	import flash.events.Event;
	import flash.events.ProgressEvent;
	import flash.net.FileFilter;
	import flash.net.FileReference;
	import flash.net.URLRequest;
	import flash.net.URLRequestMethod;
	import flash.utils.Dictionary;
	
	import models.State;
	import models.Variable;
	
	import mx.collections.ArrayCollection;
	import mx.core.Application;
	import mx.rpc.events.ResultEvent;
	import mx.rpc.http.HTTPService;
	
	import ui.notifications.ErrorNotification;
	import ui.windows.ProgressWindow;
	
	import utils.BlockingHTTPService;
	import utils.MessageBus;
	
	public class SocketController extends Object
	{
		
		public function SocketController()
		{
			super();
		}
		
		// Begin recording states
		public function record(args:String = "", copyState:Boolean = true):void{
			var service:BlockingHTTPService = new BlockingHTTPService();
			service.url = Config.serverURL + "/record";
			
			var params:Object = new Object();
			params.file_name = Application.application.sourceController.sourceFile.nativePath;
			params.args = args;
			params.source = Application.application.sourceController.source;
			
			service.resultFormat = "xml";
			service.addEventListener(ResultEvent.RESULT, onRecordResult);
			service.send(params);
			
			if(!Config.lockGUIWhileRecord)
				MessageBus.post("Record began in background...");
		}
		
		// Invoked when "record" request got response
		private function onRecordResult(evt:ResultEvent):void{
			if(!Config.lockGUIWhileRecord)
				MessageBus.post("Record finished in background");			
			
			var result:XML = XML(evt.result);
			
			// Timeline
			Application.application.timeline.totalStages = Number(result.@size -1);
			Application.application.timeline.timelineMin = 0;
			Application.application.timeline.timelineMax = Number(result.@size - 1);
			Application.application.timelineController.generateTimelineRange();
			
			// Extract console output
			var consoleCode:String = String(result.console[0]);			
			var consoleLines:Array = consoleCode.split("\n");
			Application.application.consoleOutput = new ArrayCollection(consoleLines);
			Application.application.mainView.consoleView.consoleList.dataProvider = Application.application.consoleOutput;
			
			// Get info for State 0 and make State 0 the current state
			var state:State = new State();
			state.id = 0;
			Application.application.pyStates.currentState = state;
			
			for(var i:uint=0;i<result.variables[0].children().length();i++){
				var entry:Object = result.variables[0].variable[i];
				var variable:Variable = new Variable();
				variable.name = entry.@name;
				variable.value = entry.@value;
				variable.scope = entry.@scope; 

				state.variables.addItem(variable);
				
				// Cache variable
				Application.application.historyController.cacheVariable(0, variable);
			}
			
			// Cache the state
			Application.application.historyController.cacheState(state);
			
			// Trasnsfer the statefile back from server
			if(Config.copyStateFile)
				downloadStateFile();
		}
		
		// Download the state file from server
		private function downloadStateFile():void{
			MessageBus.post("Downloading state file from server ...");
			
			var request:URLRequest = new URLRequest();
			request.url = Config.serverURL + "/retrieve_state_file";
			
			var file:FileReference = new FileReference();
			file.addEventListener(Event.COMPLETE, onDownloadStateFileComplete);
			file.download(request, Application.application.sourceController.fileName + "s");
		}
		
		private function onDownloadStateFileComplete(evt:Event):void{
			MessageBus.post("State file downloaded from server");
		}
		
		public function getStateByID(id:Number):void{			
			// Update global position
			var globalPosition:int = int(Number(id / Application.application.timeline.totalStages) * 100);
			Application.application.timeline.globalTimelinePosition = globalPosition;
			
			// Try to read from cache
			var state:State = Application.application.historyController.getStateFromCache(id);
			if(state){
				changeToState(state);
				return;
			}
			
			// If the state is not in cache, then we have to get it from server
			var service:BlockingHTTPService = new BlockingHTTPService();
			service.url = Config.serverURL + "/get_state/" + int(id).toString();
			service.resultFormat = "xml";
			service.addEventListener(ResultEvent.RESULT, onGetStateResult);
			service.send();
		}
		
		private function onGetStateResult(evt:ResultEvent):void{
			var result:XML = XML(evt.result);

			var state:State = new State();
			state.id = result.@id;
			state.lineNumber = result.@lineno;
			state.consoleLineNumber = result.@console_id;
			
			for(var i:uint=0;i<result.variables[0].children().length();i++){
				var entry:Object = result.variables[0].variable[i];
				var variable:Variable = new Variable();
				variable.name = String(entry.@name);
				variable.value = String(entry.@value);
				variable.scope = String(entry.@scope); 
				
				state.variables.addItem(variable);
				
				// Cache variable
				Application.application.historyController.cacheVariable(state.id, variable);
				
				// Update and cache watch (if the variable is being watched)
				var watch:Variable = Application.application.watches.findWatchByName(variable.name);				
				if(watch){
					watch.value = variable.value;
					Application.application.historyController.cacheVariable(state.id, watch);
				}					
				
			}
			
			// Cache state
			Application.application.historyController.cacheState(state);
			
			// Update the selected variable history (if any)
			if(Application.application.selectedVariable)
				Application.application.historyController.updateSelectedVariableHistory();
			
			changeToState(state);
		}
		
		// Chante the current state
		private function changeToState(state:State):void{
			Application.application.pyStates.currentState = state;
			
			// Navigate to the source line
			navigateToLine(state.lineNumber);
			
			// Navigate to the respectiveful console line
			navigateToConsoleLine(state.consoleLineNumber);
			
			// Notify state change
			MessageBus.post("Changed to stage [" + state.id.toString() + "]");
		}
		
		public function navigateToLine(lineNumber:Number):void{
			Application.application.mainView.sourceView.sourceList.verticalScrollPosition = lineNumber;
			Application.application.mainView.sourceView.selectedIndex = lineNumber;
		}

		public function navigateToConsoleLine(lineNumber:Number):void{
			if(lineNumber != -1){
				Application.application.mainView.consoleView.consoleList.scrollToIndex(lineNumber);
				Application.application.mainView.consoleView.consoleList.selectedIndex = lineNumber;
			}
		}

		// Upload a state file to server
		private var uploadProgressWindow:ProgressWindow;
		private var uploadFile:FileReference;
		
		public function loadStateFile():void{
			uploadFile = new FileReference();
			var filter:FileFilter = new FileFilter("Python State Files (*.pys)", "*.pys");
			uploadFile.addEventListener(Event.SELECT,onStateFileSelected);
			uploadFile.addEventListener(ProgressEvent.PROGRESS, onStateFileUploadProgress);
			uploadFile.addEventListener(DataEvent.UPLOAD_COMPLETE_DATA, onStateFileUploadComplete);
			
			uploadFile.browse([filter]);
		}
		
		private function onStateFileSelected(evt:Event):void{
			uploadProgressWindow = new ProgressWindow();
			uploadProgressWindow.popUp();
			
			var urlRequest:URLRequest = new URLRequest(Config.serverURL + "/open_state_file");
			urlRequest.method = URLRequestMethod.POST;
			uploadFile.upload(urlRequest);
		}
		
		private function onStateFileUploadProgress(evt:ProgressEvent):void{
			var percentage:Number = int(Number(evt.bytesLoaded / evt.bytesTotal) * 100);
			uploadProgressWindow.progress = percentage;
		}
		
		private function onStateFileUploadComplete(evt:DataEvent):void{
			uploadProgressWindow.progress = 100;
			uploadProgressWindow.close();

			var result:XML = XML(evt.data);
			
			// Highlight syntax for source
			var sourceCode:String = String(result.source_code[0]);
			Application.application.sourceController.processSourceCode(sourceCode);
			
			// Extract console output
			var consoleCode:String = String(result.console[0]);			
			var consoleLines:Array = consoleCode.split("\n");
			Application.application.consoleOutput = new ArrayCollection(consoleLines);
			Application.application.mainView.consoleView.consoleList.dataProvider = Application.application.consoleOutput;
			
			// Timeline
			Application.application.timeline.totalStages = Number(result.@size -1);
			Application.application.timeline.timelineMin = 0;
			Application.application.timeline.timelineMax = Number(result.@size - 1);
			Application.application.timelineController.generateTimelineRange();
			
			// Get info for State 0 and make State 0 the current state
			var state:State = new State();
			state.id = 0;
			Application.application.pyStates.currentState = state;
			for(var i:uint=0;i<result.variables[0].children().length();i++){
				var entry:Object = result.variables[0].variable[i];
				var variable:Variable = new Variable();
				variable.name = entry.@name;
				variable.value = entry.@value;
				variable.scope = entry.@scope; 
				
				state.variables.addItem(variable);
				
				// Cache variable
				Application.application.historyController.cacheVariable(0, variable);
			}
			
			// Cache the state
			Application.application.historyController.cacheState(state);
		}
		
		// Execute a query
		public function doQuery(expr:String):void{
			var service:BlockingHTTPService = new BlockingHTTPService();
			service.url = Config.serverURL + "/query";
			service.resultFormat = "xml";
			service.addEventListener(ResultEvent.RESULT, onQueryResult);
			
			var params:Object = new Object();
			params.expr = expr;
			
			service.send(params);
		}
		
		private function onQueryResult(evt:ResultEvent):void{
			var result:XML = XML(evt.result);
			
			Application.application.queryResult = new ArrayCollection();
			
			var numStates:Number = result.children().length();
			if(numStates == 0){
				var alert:ErrorNotification = new ErrorNotification();
				alert.text = "Sorry, the query returned 0 results";
				alert.popUp();
				return;
			}
			
			for(var i:uint = 0; i< numStates; i++){
				var stateId:Number = Number(result.bid[i]);
				Application.application.queryResult.addItem(stateId);
			}
			
			// Go to query result view
			Application.application.timelineView.queryResultWidget.rebuildView();
			Application.application.timelineView.activeView.selectedIndex = 1;
			
			MessageBus.post("Query returned " + numStates + " results");
		}
		
	}
}